package com.starleague.battletech.unit.mech.jpa.appengine;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;

import com.google.appengine.datanucleus.annotations.Unowned;
import com.starleague.battletech.game.IMutableBattletechTeam;
import com.starleague.battletech.game.jpa.appengine.BattletechGameGae;
import com.starleague.battletech.game.jpa.appengine.BattletechTeamGae;
import com.starleague.battletech.unit.BattletechUnitMovementType;
import com.starleague.battletech.unit.IWeight;
import com.starleague.battletech.unit.jpa.appengine.BattletechUnitGae;
import com.starleague.battletech.unit.jpa.appengine.PilotGae;
import com.starleague.battletech.unit.mech.IBipedMech;
import com.starleague.battletech.unit.mech.IMutableMech;
import com.starleague.battletech.unit.mech.IQuadrupedMech;
import com.starleague.battletech.unit.mech.MechHitLocationPosition;
import com.starleague.battletech.unit.mech.MechMovementMode;
import com.starleague.battletech.unit.mech.dto.IMechDto;
import com.starleague.hexagonal.flat.FlatHexDirection;

@Entity(name = "MECH")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class MechGae
        extends BattletechUnitGae
        implements IBipedMech, IQuadrupedMech, IMutableMech, IMechDto
{
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private BattletechGameGae game;

    @Basic(fetch = FetchType.LAZY, optional = true)
    @Unowned
    private BattletechTeamGae team;

    @Basic(optional = false)
    private int pilotingSkill;

    @Basic(optional = true)
    @Column(nullable = true)
    @Enumerated(EnumType.STRING)
    private FlatHexDirection facingDirection;

    @Basic(optional = true)
    @Column(nullable = true)
    @Enumerated(EnumType.STRING)
    private MechMovementMode movementMode;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private MechInternalStructureGae internalStructure;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private MechEngineGae engine;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private MechGyroGae gyro;

    @OneToMany(mappedBy = "mech", cascade = CascadeType.ALL)
    private List<MechHitLocationGae> hitLocations;

    MechGae(BattletechGameGae game,
            BattletechTeamGae team,
            PilotGae pilot,
            IWeight weight,
            BattletechUnitMovementType movementType)
    {
        super(pilot, weight, movementType);
        this.game = game;
        this.team = team;
        this.hitLocations = new ArrayList<>();
    }

    protected MechGae()
    {
        //
    }

    public BattletechGameGae getGame()
    {
        return this.game;
    }

    @Override
    public BattletechTeamGae getTeam()
    {
        return this.team;
    }

    @Override
    public void setTeam(IMutableBattletechTeam team)
    {
        this.team = (BattletechTeamGae) team;
    }

    //
    // Movement / Position
    //
    @Override
    public FlatHexDirection getFacingDirection()
    {
        return this.facingDirection;
    }

    @Override
    public void setFacingDirection(FlatHexDirection facingDirection)
    {
        this.facingDirection = facingDirection;
    }

    @Override
    public MechMovementMode getMovementMode()
    {
        return this.movementMode;
    }

    @Override
    public void setMovementMode(MechMovementMode movementMode)
    {
        this.movementMode = movementMode;
    }

    @Override
    public boolean hasMoved()
    {
        return (this.movementMode != null);
    }

    @Override
    public void clearMovement()
    {
        super.clearMovement();
        this.movementMode = null;
    }

    //
    // Hit Locations
    //

    public List<MechHitLocationGae> getHitLocations()
    {
        return this.hitLocations;
    }

    @Override
    public MechHitLocationGae getHead()
    {
        return getHitLocation(MechHitLocationPosition.HEAD);
    }

    @Override
    public MechHitLocationGae getCenterTorso()
    {
        return getHitLocation(MechHitLocationPosition.CENTER_TORSO);
    }

    @Override
    public MechHitLocationGae getLeftTorso()
    {
        return getHitLocation(MechHitLocationPosition.LEFT_TORSO);
    }

    @Override
    public MechHitLocationGae getRightTorso()
    {
        return getHitLocation(MechHitLocationPosition.RIGHT_TORSO);
    }

    @Override
    public MechHitLocationGae getLeftArm()
    {
        return getHitLocation(MechHitLocationPosition.LEFT_ARM);
    }

    @Override
    public MechHitLocationGae getRightArm()
    {
        return getHitLocation(MechHitLocationPosition.RIGHT_ARM);
    }

    @Override
    public MechHitLocationGae getLeftLeg()
    {
        return getHitLocation(MechHitLocationPosition.LEFT_LEG);
    }

    @Override
    public MechHitLocationGae getRightLeg()
    {
        return getHitLocation(MechHitLocationPosition.RIGHT_LEG);
    }

    @Override
    public MechHitLocationGae getLeftLegBack()
    {
        return getHitLocation(MechHitLocationPosition.LEFT_LEG_BACK);
    }

    @Override
    public MechHitLocationGae getRightLegBack()
    {
        return getHitLocation(MechHitLocationPosition.RIGHT_LEG_BACK);
    }

    @Override
    public MechHitLocationGae getLeftLegFront()
    {
        return getHitLocation(MechHitLocationPosition.LEFT_LEG_FRONT);
    }

    @Override
    public MechHitLocationGae getRightLegFront()
    {
        return getHitLocation(MechHitLocationPosition.RIGHT_LEG_FRONT);
    }

    //
    // Core Components
    //

    @Override
    public MechCoreComponentGae getCockpit()
    {
        return getHead().getCoreComponent(MechCoreComponentGae.Type.COCKPIT);
    }

    @Override
    public MechCoreComponentGae getLifeSupport()
    {
        return getHead().getCoreComponent(MechCoreComponentGae.Type.LIFE_SUPPORT);
    }

    @Override
    public MechCoreComponentGae getSensors()
    {
        return getHead().getCoreComponent(MechCoreComponentGae.Type.SENSORS);
    }

    @Override
    public MechInternalStructureGae getInternalStructure()
    {
        return this.internalStructure;
    }

    public void setInternalStructure(MechInternalStructureGae internalStructure)
    {
        this.internalStructure = internalStructure;
    }

    @Override
    public MechEngineGae getEngine()
    {
        return this.engine;
    }

    public void setEngine(MechEngineGae engine)
    {
        this.engine = engine;
    }

    @Override
    public MechGyroGae getGyro()
    {
        return this.gyro;
    }

    public void setGyro(MechGyroGae gyro)
    {
        this.gyro = gyro;
    }

    private MechHitLocationGae getHitLocation(MechHitLocationPosition position)
    {
        for (MechHitLocationGae hitLocation : this.hitLocations)
        {
            if (position == hitLocation.getPosition())
            {
                return hitLocation;
            }
        }
        return null;
    }

    /**
     * Find all CriticalSlots for the given position.
     * 
     * @param position
     * @return
     */
    MechCriticalSlotGae[] getCriticalSlotsFor(MechHitLocationPosition position)
    {
        MechCriticalSlotGae[] positionCriticalSlots = new MechCriticalSlotGae[position.getMechHitLocationType().getNumCriticalSlots()];

        // TODO: Make sure indices don't conflict!!

        MechHitLocationGae hitLocation = getHitLocation(position);
        for (MechCoreComponentGae coreComponent : hitLocation.getCoreComponents())
            for (MechCriticalSlotGae criticalSlot : coreComponent.getCriticalSlots())
                // if (criticalSlot.getPosition() == position) <= This condition is unnecessary
                positionCriticalSlots[criticalSlot.getIndex()] = criticalSlot;

        if (this.engine != null)
            for (MechCriticalSlotGae criticalSlot : this.engine.getCriticalSlots())
                if (criticalSlot.getPosition() == position)
                    positionCriticalSlots[criticalSlot.getIndex()] = criticalSlot;

        if (this.gyro != null)
            for (MechCriticalSlotGae criticalSlot : this.gyro.getCriticalSlots())
                if (criticalSlot.getPosition() == position)
                    positionCriticalSlots[criticalSlot.getIndex()] = criticalSlot;

        return positionCriticalSlots;
    }

    /**
     * Find an unused CriticalSlot for the given position.
     * 
     * @param position
     * @return
     */
    Integer getFreeCriticalSlotFor(MechHitLocationPosition position)
    {
        MechCriticalSlotGae[] positionCriticalSlots = getCriticalSlotsFor(position);
        for (int i = 0; i < positionCriticalSlots.length; ++i)
            if (positionCriticalSlots[i] == null)
                return i;

        return null;
    }

    //
    // @Override
    // public BattletechPointsGae getMovementPoints()
    // {
    // return this.movementPoints;
    // }
    //
    // public void setMovementPoints(BattletechPointsGae movementPoints)
    // {
    // this.movementPoints = movementPoints;
    // }

    @Override
    public Map<String, Object> infoProperties()
    {
        Map<String, Object> infoPropertyMap = super.infoProperties();
        infoPropertyMap.put("team", this.team);
        infoPropertyMap.put("pilotingSkill", this.pilotingSkill);
        infoPropertyMap.put("facingDirection", this.facingDirection);
        infoPropertyMap.put("movementMode", this.movementMode);
        infoPropertyMap.put("internalStructure", this.internalStructure);
        infoPropertyMap.put("engine", this.engine);
        infoPropertyMap.put("gyro", this.gyro);
        infoPropertyMap.put("hitLocations", this.hitLocations);
        return infoPropertyMap;
    }
}
